#include "znx_hash_tab.h"


znx_hash_tab_t *
znx_hash_tab_create(uint64_t bucket_num, uint64_t slot_num,
    znx_hash_node_cmp_pt cmp_handler,
    znx_hash_node_free_handle free_handle)
{
    uint64_t            i, j;
    znx_hash_tab_t      *tab;
    znx_hash_bucket_t   *bucket;
    znx_queue_t         *q;

    if (bucket_num < 1) {
        bucket_num = 1;
    }

    if (slot_num < 32) {
        slot_num = 32;
    }

    tab = znx_thread_calloc(sizeof(znx_hash_tab_t));
    tab->buckets = znx_thread_calloc(sizeof(znx_hash_bucket_t) * bucket_num);
    tab->bucket_num = bucket_num;
    tab->node_cmp = cmp_handler;
    tab->free_handle = free_handle;

    for (i = 0; i < bucket_num; i++) {
        bucket = tab->buckets + i;
        bucket->tab = tab;
        bucket->slot_num = slot_num;
        bucket->slots = znx_thread_malloc(sizeof(znx_queue_t) * slot_num);
        for (j = 0; j < slot_num; j++) {
            q = bucket->slots + j;
            znx_queue_init(q);
        }
    }

    return tab;
}



void
znx_hash_tab_destroy(znx_hash_tab_t *tab)
{
    uint64_t            i, j;
    znx_hash_bucket_t   *bucket;
    znx_queue_t         *slot, *q;
    znx_hash_node_t     *node;

    if (tab->buckets == NULL) {
        return;
    }

    for (i = 0; i < tab->bucket_num; i++) {
        bucket = tab->buckets + i;
        if (bucket->slots == NULL) {
            continue;
        }

        if (tab->free_handle) {
            for (j = 0; j < bucket->slot_num; j++) {
                slot = bucket->slots + j;
                while (!znx_queue_empty(slot)) {
                    q = znx_queue_last(slot);
                    znx_queue_remove(q);
                    node = znx_queue_data(q, znx_hash_node_t, queue);
                    tab->free_handle(node);
                }
            }
        }

        znx_thread_free(bucket->slots);
    }

    znx_thread_free(tab->buckets);
    znx_thread_free(tab);
}


static void
znx_hash_bucket_realloc(znx_hash_bucket_t *bucket)
{
    uint64_t        i;
    uint64_t        new_slot_num;
    znx_queue_t     *new_slots, *new_slot;
    znx_queue_t     *slot, *q;
    znx_hash_node_t *node; 

    new_slot_num = bucket->slot_num * 2;
    new_slots = znx_thread_malloc(sizeof(znx_queue_t) * new_slot_num);

    for (i = 0; i < new_slot_num; i++) {
        znx_queue_init(new_slots + i);
    }

    for (i = 0; i < bucket->slot_num; i++) {
        slot = bucket->slots + i;
        while (!znx_queue_empty(slot)) {
            q = znx_queue_last(slot);
            znx_queue_remove(q);
            node = znx_queue_data(q, znx_hash_node_t, queue);
            new_slot = new_slots + (node->hash_key % new_slot_num);
            znx_queue_insert_after(new_slot, &node->queue);
        }
    }

    znx_thread_free(bucket->slots);
    bucket->slots = new_slots;
    bucket->slot_num = new_slot_num;
}


static void
znx_hash_bucket_add(znx_hash_bucket_t *bucket, znx_hash_node_t *node)
{
    if (bucket->count > (bucket->slot_num * 7 / 10)) {
        znx_hash_bucket_realloc(bucket);
    }

    znx_queue_t             *slot;
    slot = bucket->slots + (node->hash_key % bucket->slot_num);
    znx_queue_insert_after(slot, &node->queue);
    bucket->count++;
}


static znx_hash_node_t *
znx_hash_bucket_get(znx_hash_bucket_t *bucket, znx_hash_node_t *target)
{
    znx_queue_t             *slot;
    znx_queue_t             *q;
    znx_hash_node_t         *node;

    slot = bucket->slots + (target->hash_key % bucket->slot_num);
    q = znx_queue_head(slot);
    while (q != slot) {
        node = znx_queue_data(q, znx_hash_node_t, queue);
        q = q->next;
        if (node->hash_key != target->hash_key) {
            continue;
        }
        if (bucket->tab->node_cmp(node, target) == 0) {
            return node;
        }
    }

    return NULL;
}


static znx_hash_node_t *
znx_hash_bucket_remove(znx_hash_bucket_t *bucket, znx_hash_node_t *target)
{
    znx_hash_node_t     *node;
    node = znx_hash_bucket_get(bucket, target);
    if (node == NULL) {
        return NULL;
    }

    znx_queue_remove(&node->queue);
    return node;
}


void
znx_hash_tab_add(znx_hash_tab_t *tab, znx_hash_node_t *node)
{
    znx_hash_bucket_t   *bucket;
    bucket = tab->buckets + (node->hash_key % tab->bucket_num);
    znx_hash_bucket_add(bucket, node);
}


znx_hash_node_t *
znx_hash_tab_get(znx_hash_tab_t *tab, znx_hash_node_t *target)
{
    znx_hash_bucket_t   *bucket;
    bucket = tab->buckets + (target->hash_key % tab->bucket_num);
    return znx_hash_bucket_get(bucket, target);
}


znx_hash_node_t *
znx_hash_tab_remove(znx_hash_tab_t *tab, znx_hash_node_t *target)
{
    znx_hash_bucket_t   *bucket;
    bucket = tab->buckets + (target->hash_key % tab->bucket_num);
    return znx_hash_bucket_remove(bucket, target);
}